<?php


namespace App\Repositories\Gashapon;

use App\Exceptions\BasicException;
use App\Models\MainDB\Gashapon\Pool;
use App\Models\MainDB\GiftBackpack;
use App\Repositories\GiftRepository;
use App\Services\Tools\PushChatService;
use App\Services\Tools\PushService;
use App\Services\Tools\RedisService;
use App\Traits\Singleton;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;

class LotteryRebuildRepository
{
    use Singleton;

    // 验证是否抽取下期奖品
    protected function checkDrawTemplate(Collection $pool, int $drawCount, $setting, $user): bool
    {
        $count = $pool->where('count', '>', 0)->sum('count');
        if ($drawCount > $count) { // 奖池的剩余礼物不足抽取次数
            return true;
        }
        $mediumPrizeCoin    = $setting->medium_prize_coin;
        $smallerPrizesCount = $pool->where('count', '>', 0)->where('price', '<', $mediumPrizeCoin)->sum('count');
        $largerPrizesCount  = $pool->where('count', '>', 0)->where('price', '>=', $mediumPrizeCoin)->sum('count');

        if ($user->is_sanction && $smallerPrizesCount < $drawCount) { // 被制裁, 并且小礼物数量不足抽取数量
            return true;
        }
        if ($largerPrizesCount > 0 && $drawCount >= ($smallerPrizesCount + 1)) { // 大于131400的礼物数量 > 0 并且  抽取数量 > (小于131400礼物数量 + 1)
            return true;
        }
        return false;
    }

    /**
     * @throws \App\Exceptions\BasicException
     */
    public function draw(int $userId, int $count)
    {
        DB::beginTransaction();
        try {
            $setting    = SettingRepository::getInstance()->setting(true);
            $drawOutput = 0;
            $drawInput  = $count * $setting->balance_coin;
            $user       = UserRepository::getInstance()->drawUser($setting, $userId, true);
            // 验证余额
            if ($user->balance < $count) {
                throw new BasicException(-1, '余额不足');
            }

            //判断用户是否抽取队列
            $userPoolQueue = PoolQueueRepository::getInstance()->info(["user_id" => $userId, "draw_count" => $count, "status" => 0], 'priority', 'desc', true);

            if (!empty($userPoolQueue)) {
                $userPool   = PoolRepository::getInstance()->one(['type' => Pool::QUEUE_TYPE]);
                $drawPrizes = json_decode($userPoolQueue['prize_group'], true);
            } else {
                // 获取用户抽取奖池
                $userPool           = PoolRepository::getInstance()->getUserPool($user, true, true);
                $luckyPrizeInterval = $userPool->lucky_prize_interval; // 最大礼物区间
                if ($luckyPrizeInterval <= 0) {
                    $luckyPrizeInterval = mt_rand(min($userPool->lucky_prize_min_interval, $userPool->lucky_prize_max_interval), max($userPool->lucky_prize_min_interval, $userPool->lucky_prize_max_interval));
                }
                // 中等礼物
                if ($userPool->medium_lucky_prize_interval == 0) {
                    $userPool->medium_lucky_prize_interval = mt_rand(1, $userPool->medium_prize_max_interval);
                }
                // 中等礼物计步器
                $isDrawMediumPrize = true;
                if ($userPool->medium_lucky_score < $userPool->medium_lucky_prize_interval) {
                    if ($count >= 100) {
                        $userPool->medium_lucky_score += 10;
                    } elseif ($count >= 10) {
                        $userPool->medium_lucky_score += 1;
                    }
                    $isDrawMediumPrize = false;
                }

                $luckyScore = $userPool->lucky_score;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // 当前幸运值
                // 奖池奖品
                $poolPrizes = PoolPrizeRepository::getInstance()->getPoolPrize($userPool->id);
                // 抽奖
                $drawSetting                                    = $setting;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                // 抽奖设置
                $drawSetting->lucky_score                       = $luckyScore; // 幸运值
                $drawSetting->lucky_prize_interval              = $luckyPrizeInterval; // 幸运奖品间隔
                $drawSetting->lucky_prize_date_min_input        = $userPool->lucky_prize_date_min_input; // 出幸运大奖条件(用户每日最小投入
                $drawSetting->medium_lucky_prize_date_min_input = $userPool->medium_lucky_prize_date_min_input; // 出中等礼物条件(用户每日最小投入
                $drawSetting->medium_prize_coin                 = $userPool->medium_prize_coin;
                $drawSetting->special_prize_coin                = $userPool->special_prize_coin;
                $drawSetting->lucky_prize_coin                  = $userPool->lucky_prize_coin;
                $drawSetting->lucky_score                       = $userPool->lucky_score;
                $drawSetting->lucky_prize_interval              = $userPool->lucky_prize_interval;

                if (!$this->checkDrawTemplate($poolPrizes, $count, $setting, $user)) { // 可以抽本期奖池
                    $drawTemplatePrizes = [];
                    $drawPoolPrizes     = $this->drawPrizes($user, $poolPrizes, $count, $drawSetting, $isDrawMediumPrize); // 改为只有礼物充足才抽本轮奖池
                } else {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       // 抽取下期奖池
                    $drawPoolPrizes = [];
                    // 改为礼物不充足, 只抽模板礼物， 将剩余的礼物增加到原奖池
                    $templatePrizes   = PoolTemplateRepository::getInstance()->getPoolTemplate($userPool->id);
                    $poolUpdatePrizes = $templatePrizes->pluck('count', 'prize_id')->toArray();
                    // $drawTemplatePrizes = $this->drawPrizes($user, $templatePrizes, $count - $poolPrizesCount, $drawSetting); // 先抽本期， 再抽模板
                    $drawTemplatePrizes    = $this->drawPrizes($user, $templatePrizes, $count, $drawSetting, $isDrawMediumPrize);                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         // 只抽模板
                    $userPool->round    += 1;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          // 奖池轮数增加
                    $userPool->prize_count = array_sum($poolUpdatePrizes);                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             // 奖池重制, 计算本期奖池的奖品总数.
                    // 创建下一轮奖品
                    PoolPrizeRepository::getInstance()->createNextRounPrizes($userPool->id, $poolUpdatePrizes);
                    // 上一期奖励抽空, 清空幸运值
                    $luckyScore = 0;
                }
                $drawPrizes = $this->appendPrizes($drawPoolPrizes, $drawTemplatePrizes);
            }
            $drawPrizesCount = array_sum($drawPrizes);
            if ($drawPrizesCount != $count) {
                throw new BasicException(-1, '扭蛋机异常');
            }
            $gifts = (new GiftRepository())->giftsByIds(array_keys($drawPrizes)); // 抽中礼物信息
            if ($gifts->where('price', '>=', $userPool->lucky_prize_coin)->whereIn('id', array_keys($drawPrizes))->isNotEmpty()) {
                // 抽中幸运礼物, 清空幸运值
                $luckyScore = 0;
            } else {
                $luckyScore += $drawPrizesCount; // 未抽中幸运礼物, 增加幸运值
            }
            $notifyCount      = $broadcastCount = 0;                                                                                                                                     // 总数量.(通知,广播)
            $drawPrizesRecord = [];                                                                                                                                                      // 抽取记录
            // ======================================== 记录 ========================================
            // 1. 抽奖记录
            $draw           = DrawRepository::getInstance()->create(['user_id' => $userId, 'count' => $count, 'prize_count' => $drawPrizesCount, 'input' => $drawInput, 'output' => $drawOutput]); // 抽奖记录
            $notifyPushData = $broadcastPushData = [];                                                                                                                                   // 推送数据
            foreach ($drawPrizes as $prizeId => $prizeCount) {
                $gift = $gifts->where('id', $prizeId)->first();
                if ($userPool->type != Pool::QUEUE_TYPE) {
                    if ($gift->price >= $userPool->medium_prize_coin && $gift->price < $userPool->special_prize_coin) { // 出中等礼物.
                        $userPool->medium_lucky_score          = 0;                                                              // 计数器设置为0
                        $userPool->medium_lucky_prize_interval = mt_rand(1, $userPool->medium_prize_max_interval);      // 重新生成随机间隔
                    }
                }
                $drawOutput += $gift->price * $prizeCount;
                if ($gift->price >= $setting->notify_coin) {
                    $notifyCount       += $prizeCount;
                    $notifyGift  = Collection::make([]);
                    $notifyGift->name = $gift->name ?? '';
                    $notifyGift->price = $gift->price;
                    $notifyGift->count = $prizeCount;

                    $notifyPushData[] = $notifyGift;
                }
                if ($gift->price >= $setting->broadcast_coin) {
                    $broadcastCount += $prizeCount;

                    $broadcastGift          = Collection::make([]);
                    $broadcastGift->name = $gift->name ?? '';
                    $broadcastGift->price = $gift->price;
                    $broadcastGift->count = $prizeCount;
                    $broadcastGift->picture = $gift->picture ?? '';
                    $broadcastPushData[]    = $broadcastGift;
                }
                $drawPrizesRecord[] = ['user_id' => $userId, 'draw_id' => $draw->id, 'prize_id' => $prizeId, 'prize_price' => $gift->price, 'count' => $prizeCount,];
            }
            // 2. 余额使用日志
            BalanceLogRepository::getInstance()->draw($userId, $count, $user->balance);
            // 3. 更新抽奖产出
            $draw->output += $drawOutput;
            $draw->save();
            // 4. 抽奖详情
            DrawRecordRepository::getInstance()->insert($drawPrizesRecord);
            // 5. 更新奖池, 部分数据在替换奖池的时候设置
            $userPool->count  += $drawPrizesCount;
            $userPool->input += $drawInput;
            $userPool->output += $drawOutput;

            if ($userPool->type != Pool::QUEUE_TYPE) {
                $userPool->lucky_prize_interval = $luckyPrizeInterval;
                $userPool->lucky_score          = $luckyScore;
            }

            $userPool->save();
            // 6. 奖池礼物更新
            if ($userPool->type != Pool::QUEUE_TYPE) {
                PoolPrizeRepository::getInstance()->batchDecPrizesByPoolId($userPool->id, $drawPrizes);
            } else {
                $userPoolQueue->status = 1;
                $userPoolQueue->save();
            }
            // 7. 用户统计
            UserRepository::getInstance()->drawUpdate($userId, $drawInput, $drawOutput, $drawPrizesCount);
            // 8. 用户幸运统计
            if ($notifyCount >= 1) {
                LuckyStatDateRepository::getInstance()->drawUpdate($userId, $notifyCount, $broadcastCount);
            }
            // 9. 用户背包
            (new GiftBackpack())->addDraws($userId, $drawPrizes);
            // 10. 减少余额, 等级变更
            unset($user->game);
            $user->input   += $drawInput;
            $user->output += $drawOutput;
            $user->count  += $drawPrizesCount;
            $user->balance -= $count;
            $user->exp     += $count;                                                                    // 用户经验
            $level         = UserRepository::getInstance()->checkNextLevelExp($user->level, $user->exp); // 用户等级
            $user->level   = $level;
            $user->save();
            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            throw new BasicException($e->getCode(), $e->getMessage());
        }
        // 11. 推送
        $this->push($userId, $notifyPushData, $broadcastPushData);
        return $draw;
    }

    protected function push(int $userId, $notifyPushData, $broadcastPushData)
    {
        try {
            $user = RedisService::getUserChatLabel($userId);
            foreach ($notifyPushData as $value) {
                (new PushChatService())->gashapon($user, $value);
            }
            (new PushService())->gashapon($broadcastPushData, $user);
        } catch (\Exception $e) {
            return false;
        }
        return true;
    }

    // 追加奖励
    protected function appendPrizes(array $prizes, array $append)
    {
        foreach ($append as $prizeId => $prizeCount) {
            $prizes[$prizeId] = isset($prizes[$prizeId]) ? $prizes[$prizeId] + $prizeCount : $prizeCount;
        }
        return $prizes;
    }

    // 抽取奖品
    protected function drawPrizes($user, $pool, int $drawCount, $setting, bool $isDrawMediumPrize): array
    {
        $isSanction = $user->is_sanction;
        $count      = $drawCount;
        if ($count <= 0) {
            return [];
        }
        $poolCount = $pool->where('count', '>', 0)->sum('count');

        if ($poolCount <= $count) {
            return $pool->where('count', '>', 0)->pluck('count', 'prize_id')->toArray();
        }
        $mediumPrizes                 = $specialPrizes = [];
        $specialPrizesCount = 0;
        $userDateInput      = $user->date->input;
        $luckyPrizeDateMinInput = $setting->lucky_prize_date_min_input;
        $mediumLuckyPrizeDateMinInput = $setting->medium_lucky_prize_date_min_input;
        if (($userDateInput >= $luckyPrizeDateMinInput) && $isSanction == false && $count > 1) { // 抽取幸运礼物(没有被制裁)
            $specialPrizes      = $this->specialSub($pool, $count, $setting);                         // 特殊礼物抽取
            $specialPrizesCount = array_sum($specialPrizes);
        }
        $count -= $specialPrizesCount;

        if (($userDateInput >= $mediumLuckyPrizeDateMinInput) && $isSanction == false && $isDrawMediumPrize && $specialPrizesCount < 1) { // 可以抽中等礼物, 并且没有抽到特殊礼物(没有被制裁)
            $mediumPrizes      = $this->mediumSub($pool, $count, $setting);
            $mediumPrizesCount = array_sum($mediumPrizes);
            $count             -= $mediumPrizesCount;
        }

        $prizes = $this->sub($pool->where('count', '>', 0)->where('price', '<', $setting->medium_prize_coin), $drawCount, $count, $setting, $user->game->weight); // 普通礼物抽取
        // 特殊礼物追加
        foreach ($specialPrizes as $prizeId => $prizeCount) {
            $prizes[$prizeId] = isset($prizes[$prizeId]) ? $prizes[$prizeId] + $prizeCount : $prizeCount;
        }
        //  中等礼物追加
        foreach ($mediumPrizes as $prizeId => $prizeCount) {
            $prizes[$prizeId] = isset($prizes[$prizeId]) ? $prizes[$prizeId] + $prizeCount : $prizeCount;
        }
        return $prizes;
    }

    // 创建奖品数量组合
    public function createPrizesCombination(array $map, int $count, array $alonePrizeIdsMap = []): array
    {
        $prizeTotalCount = array_sum(array_pluck($map, 'count'));
        if ($prizeTotalCount <= 0) {
            return [];
        }
        if ($prizeTotalCount <= $count) {
            return $map;
        }
        $result = $pool = [];

        foreach ($map as $value) {
            $prizeId    = $value['prize_id'];
            $prizeCount = $value['count'];
            $length     = count($pool) + $prizeCount;
            $pool       = array_pad($pool, $length, $prizeId);
        }
        shuffle($pool); // 打乱奖池顺序
        while ($count) { // 结果追加打乱后的奖池第一个奖品
            $randomPrizeId = array_shift($pool);
            if (in_array($randomPrizeId, $alonePrizeIdsMap)) { // 只允许组合一个的礼物
                return [$randomPrizeId => 1];
            }
            $result[] = $randomPrizeId;
            $count--;
        }

        return array_count_values($result);
    }

    /**
     * 抽取特殊礼物
     *
     * @param Collection $pool 奖池全部奖品
     * @param int        $count 抽取数量
     * @param int        $specialPrice 特殊礼物价格
     *
     * @return array
     */
    protected function specialSub(Collection $pool, int $count, $setting)
    {
        $specialPrizeCoin   = $setting->special_prize_coin;   // 特殊礼物价值
        $luckyPrizeCoin   = $setting->lucky_prize_coin;     // 幸运礼物价值
        $luckyScore       = $setting->lucky_score;          // 幸运值
        $luckyPrizeInterval = $setting->lucky_prize_interval; // 幸运奖品间隔

        $specialPrizes           = $pool->where('price', '>=', $specialPrizeCoin);       // 特殊礼物
        $luckyPrizes   = $pool->where('price', '>=', $luckyPrizeCoin);         // 幸运礼物
        $prizesCount   = $pool->where('count', '>', 0)->sum('count');          // 奖品当前剩余数量
        $prizesTotalCount = $pool->sum('total_count');                            // 奖品总数(包含被抽的数量
        $specialPrizesCount = $specialPrizes->where('count', '>', 0)->sum('count'); // 特殊礼物当前数量
        $specialPrizesTotalCount = $specialPrizes->sum('total_count');                   // 特殊礼物总数
        if ($specialPrizesCount <= 0) { // 无特殊礼物
            return [];
        }
        $interval = $prizesTotalCount / $specialPrizesTotalCount;         // 礼物间隔 = 奖品总数 / 特殊礼物数量
        // $drawStartPosition = $prizesTotalCount - $prizesCount + 1; // 本次抽取开始位置 = 奖品总数 - 奖品剩余数量 + 1
        $drawEndPosition = $prizesTotalCount - $prizesCount + $count + 1; // 本次抽取结束位置 = 奖品总数 - 奖品剩余数量 + 本次抽取数量 + 1

        $drawPrizes = []; // 抽中礼物
        // $getCount = 0; // 抽中数量(改为只抽出一个奖品 后废弃
        while ($specialPrizesCount) {
            $number        = $specialPrizesTotalCount - $specialPrizesCount + 1; // 特殊礼物序号 = 特殊礼物总数 - 特殊礼物数量 + 1
            $startPosition = $interval * ($number - 1) + 1;                      // 奖品开始区间 = 间隔 * (序号 - 1) + 1.
            $endPosition   = $interval * $number;                                // 奖品结束区间 = 间隔 * 序号
            $position      = mt_rand($startPosition, $endPosition);              // 奖品随机位置
            // if($position <= $drawEndPosition && $getCount <= $count){ // 奖品位置 < 抽取结束位置 && 抽中数量 <= 抽取数量 为抽中 (改为只抽出一个奖品 后废弃
            if ($position <= $drawEndPosition) { // 奖品位置 < 抽取结束位置 && 抽中数量 <= 抽取数量 为抽中
                // $prizeId = $specialPrizes->where('count', '>', 0)->pluck('prize_id')->random(); // 数量>0的特殊礼物随机一个
                $luckyPrizeCount = $luckyPrizes->where('count', '>', 0)->sum('count');
                if ($luckyScore >= $luckyPrizeInterval && $luckyPrizeCount > 0) {
                    $prizeId = $pool->where('count', '>', 0)
                        ->where('price', '>=', $luckyPrizeCoin)
                        ->pluck('prize_id')
                        ->random();
                } else {
                    $specialPrizeCanDrawCount = $pool->where('count', '>', 0)
                        ->where('price', '<', $luckyPrizeCoin)
                        ->where('price', '>=', $specialPrizeCoin)->sum('count');
                    if ($specialPrizeCanDrawCount > 0) {
                        $prizeId = $pool->where('count', '>', 0)
                            ->where('price', '<', $luckyPrizeCoin)
                            ->where('price', '>=', $specialPrizeCoin)
                            ->pluck('prize_id')
                            ->random();
                    }
                }

                if (isset($prizeId)) {
                    $drawPrizes[$prizeId] = isset($drawPrizes[$prizeId]) ? $drawPrizes[$prizeId] + 1 : 1;
                    $prize                = $specialPrizes->where('prize_id', $prizeId)->first();
                    $prize->count         -= 1;
                    // $getCount++; (改为只抽出一个奖品 后废弃
                    break; // 抽中一个跳出循环
                }
            }
            $specialPrizesCount--;
        }
        return $drawPrizes;
    }

    /** 抽取中等礼物
     *
     * @param Collection $pool
     * @param int        $count
     * @param            $setting
     *
     * @return int[]
     */
    protected function mediumSub(Collection $pool, int $drawCount, $setting): array
    {
        $count = $pool
            ->where('count', '>', 0)
            ->where('price', '>=', $setting->medium_prize_coin)
            ->where('price', '<', $setting->special_prize_coin)
            ->sum('count');
        if ($count < 1) {
            return [];
        }
        // $prize = $pool
        //     ->where('count', '>', 0)
        //     ->where('price', '>=', $setting->medium_prize_coin)
        //     ->where('price', '<', $setting->special_prize_coin)
        //     ->random();
        $randomPool = $pool
            ->where('count', '>', 0)
            ->where('price', '>=', $setting->medium_prize_coin)
            ->where('price', '<', $setting->special_prize_coin);

        $lotteryPrizeId = $this->lotteryPartRandom($randomPool);
        if (empty($lotteryPrizeId)) {
            return [];
        }
        $prize = $pool->where('prize_id', $lotteryPrizeId)->first();
        if ($prize->price == $setting->medium_prize_coin && $drawCount >= 2 && $prize->count >= 2) { // 礼物价格 == 1314 并且当前剩余数量 >= 2 并且抽取数量 >= 2
            $result = [$prize->prize_id => 1 + mt_rand(0, 1)];
        } else {
            $result = [$prize->prize_id => 1];
        }
        return $result;
    }

    /**
     * 抽取小礼物
     *
     * @param Collection $pool 奖池奖品, 不包含特殊奖品
     * @param int        $count 抽取数量
     * @param int        $weight 用户权重
     *
     * @return array
     */
    protected function sub(Collection $pool, int $drawCount, int $count, $setting, int $weight = 0): array
    {
        if ($count <= 0) {
            return [];
        }

        $prizes = [];
        while ($count) {
            $count--;
            // "======================================== 第x次抽 ========================================"
            $lotteryPrizeId = $this->lotteryPartRandom($pool);
            if (empty($lotteryPrizeId)) {
                return [];
            }
            $prize = $pool->where('prize_id', $lotteryPrizeId)->first();

            $prize->count     -= 1;
            $prizeId      = $prize->prize_id;
            $prizes[$prizeId] = isset($prizes[$prizeId]) ? $prizes[$prizeId] + 1 : 1;
        }
        return $prizes;
    }


    protected function lotteryPartRandom(Collection $pool)
    {
        $prizeId = 0;
        $prizes  = [];
        $start   = 1;
        $total   = $pool->sum('count');
        $pool->sortByDesc('price')->each(function ($item) use (&$prizes, &$start) {
            $end      = $start + $item->count - 1;
            $prizes[] = [
                'prize_id' => $item->prize_id,
                'start'    => $start,
                'end'      => $end
            ];
            $start    = $end + 1;
        });
        $rand = mt_rand(1, $total);
        foreach ($prizes as $v) {
            $prizeStart = $v['start'];
            $prizeEnd   = $v['end'];
            if ($rand >= $prizeStart && $rand <= $prizeEnd) {
                $prizeId = $v['prize_id'];
                break;
            }
        }
        return $prizeId;
    }

}